package com.lam.schedule.service.impl;

import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.stream.Collectors;

import org.quartz.CalendarIntervalTrigger;
import org.quartz.CronTrigger;
import org.quartz.DailyTimeIntervalTrigger;
import org.quartz.JobExecutionContext;
import org.quartz.Scheduler;
import org.quartz.SchedulerException;
import org.quartz.SimpleTrigger;
import org.quartz.Trigger;
import org.quartz.Trigger.TriggerState;
import org.quartz.impl.jdbcjobstore.Constants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.pagehelper.Page;
import com.lam.common.base.page.PageVo;
import com.lam.common.base.page.TableHelper;
import com.lam.common.components.easyexcel.imp.model.AbstractImportBody;
import com.lam.common.components.easyexcel.imp.model.ImportEntry;
import com.lam.common.components.easyexcel.imp.model.ImportWrapper;
import com.lam.common.components.easyexcel.imp.service.AbstractImportService;
import com.lam.common.context.TokenUtils;
import com.lam.common.exception.BaseException;
import com.lam.common.exception.BusinessException;
import com.lam.common.utils.CollectionUtil;
import com.lam.common.utils.JsonUtil;
import com.lam.common.utils.StringUtils;
import com.lam.common.validate.ValidateUtil;
import com.lam.common.validate.model.UniqueDesc;
import com.lam.common.validate.model.ValidOperType;
import com.lam.framework.async.AsyncManager;
import com.lam.schedule.common.constant.ScheduleConstant;
import com.lam.schedule.common.scheduler.factory.ScheduleManage;
import com.lam.schedule.common.scheduler.model.ManualAsyncJob;
import com.lam.schedule.common.scheduler.util.TaskUtils;
import com.lam.schedule.entity.QrtzJobSchedilerState;
import com.lam.schedule.entity.ScheduleJob;
import com.lam.schedule.entity.ScheduleJobLog;
import com.lam.schedule.entity.imp.ScheduleJobImp;
import com.lam.schedule.mapper.QrtzTriggersMapper;
import com.lam.schedule.mapper.ScheduleJobMapper;
import com.lam.schedule.param.ScheduleJobParam;
import com.lam.schedule.service.ScheduleJobLogService;
import com.lam.schedule.service.ScheduleJobService;

/**
 * 定时调度任务Service业务层处理
 * 
 * @author admin
 * @date 2022-06-24
 */
@Service
public class ScheduleJobServiceImpl extends AbstractImportService<ScheduleJobImp, ScheduleJob> implements ScheduleJobService {
	
	private final Logger logger = LoggerFactory.getLogger(this.getClass());

    @Autowired
    private ScheduleJobMapper scheduleJobMapper;
    @Autowired
    private QrtzTriggersMapper qrtzTriggersMapper;
    @Autowired
    private ScheduleJobLogService scheduleJobLogService;
    @Autowired
    private ScheduleManage scheduleManage;

    /**
     * 查询定时调度
     * 
     * @param id ID
     * @return 单个对象
     */
    @Override
    @Transactional(readOnly = true)
    public ScheduleJob selectById(String id) {

        return scheduleJobMapper.selectById(id);
    }
    
    private ScheduleJob getJob(String id) {
		ScheduleJob job = selectById(id);
		if(job == null){
			logger.error("不存在的定时任务，id:{}", id);
			throw new BusinessException("该任务不存在");
		}
		return job;
	}
    
    @Override
    @Transactional(readOnly = true)
    public List<QrtzJobSchedilerState> nodeDetailList(String id){
    	
    	ScheduleJob job = getJob(id);
    	if(!TaskUtils.isActive(job)) {
    		//非调度状态的任务
    		String info = AsyncManager.getRunInfo(job.getJobKey());
    		if(StringUtils.isBlank(info)) {
    			return Collections.emptyList();
    		}
    		QrtzJobSchedilerState jobSchedilerState = new QrtzJobSchedilerState();
    		jobSchedilerState.setInstanceName(info);
    		jobSchedilerState.setSchedName(AsyncManager.getExecutorName());
    		jobSchedilerState.setTriggerState(ScheduleConstant.SH_STATUS_RUNNING);
    		return Collections.singletonList(jobSchedilerState);
    	}
    	
    	try {
    		
    		if(TaskUtils.isScheduleClustered()) {
        		//集群模式
        		TableHelper.startPage();
            	return qrtzTriggersMapper.selectNodeDetailList(scheduleManage.getScheduler().getSchedulerName(), id);
        	}
    		
    		//单机模式
			List<? extends Trigger> triggerList = scheduleManage.getTriggersOfJob(job);
			if(CollectionUtil.isEmpty(triggerList)) {
				return Collections.emptyList();
			}
			
			Scheduler scheduler = scheduleManage.getScheduler();
			List<JobExecutionContext> executingJobs = scheduler.getCurrentlyExecutingJobs();
			Map<String, QrtzJobSchedilerState> nodeDetailMap = new LinkedHashMap<>();
			for (Trigger trigger : triggerList) {
				nodeDetailMap.put(job.getJobKey(), createJobSchedilerState(job, trigger, scheduler));
			}
			QrtzJobSchedilerState jobSchedilerState;
			List<QrtzJobSchedilerState> nodeDetailList = new ArrayList<>();
			if(CollectionUtil.isNotEmpty(executingJobs)) {
				TriggerState triggerState;
				for (JobExecutionContext executingJob : executingJobs) {
					jobSchedilerState = nodeDetailMap.get(executingJob.getJobDetail().getKey().getName());
					if(jobSchedilerState != null) {
						nodeDetailMap.remove(executingJob.getJobDetail().getKey().getName());
					}else {
						jobSchedilerState = createJobSchedilerState(job, executingJob.getTrigger(), scheduler);
					}
					nodeDetailList.add(jobSchedilerState);
					jobSchedilerState.firedTime(executingJob.getFireTime());
					jobSchedilerState.schedTime(executingJob.getScheduledFireTime());
					triggerState = scheduler.getTriggerState(executingJob.getTrigger().getKey());
					jobSchedilerState.setTriggerState(TriggerState.NORMAL == triggerState?ScheduleConstant.SH_STATUS_EXECUTING: triggerState.name());
					jobSchedilerState.setPriority(executingJob.getTrigger().getPriority()+0l);
					jobSchedilerState.setNonconcurrent((executingJob.getJobDetail().isConcurrentExectionDisallowed()?
							ScheduleConstant.CONCURRENT_TYPE_N:ScheduleConstant.CONCURRENT_TYPE_Y)+"");
				}
			}
			nodeDetailList.addAll(nodeDetailMap.values());
			nodeDetailList.sort((o1, o2)->StringUtils.compare(o2.getSchedTime(), o1.getSchedTime()));
			PageVo pageVo = TableHelper.getPageVo();
			if(pageVo.getPageNum() == 1 && nodeDetailList.size() < pageVo.getPageSize()) {
				return nodeDetailList;
			}
			logger.info(nodeDetailList.size()+"");
			Page<QrtzJobSchedilerState> page = new Page<>();
			page.setPageNum(pageVo.getPageNum()).setPageSize(pageVo.getPageSize()).setTotal(nodeDetailList.size());
			page.addAll(nodeDetailList.stream().skip((pageVo.getPageNum()-1)*pageVo.getPageSize()).limit(pageVo.getPageSize()).collect(Collectors.toList()));
			return page;
		} catch (SchedulerException e) {
			logger.error("查询节点分布失败", e);
			throw new BusinessException("查询节点分布失败");
		}
    }
    
    private QrtzJobSchedilerState createJobSchedilerState(ScheduleJob job, Trigger trigger, Scheduler scheduler) throws SchedulerException {
    	QrtzJobSchedilerState jobSchedilerState = new QrtzJobSchedilerState();
		jobSchedilerState.setSchedName(scheduler.getSchedulerName());
		jobSchedilerState.setInstanceName(scheduler.getSchedulerInstanceId());
		jobSchedilerState.setTriggerName(job.getJobKey());
		jobSchedilerState.setTriggerGroup(job.getJobGroup());
		jobSchedilerState.nextFireTime(trigger.getNextFireTime());
		jobSchedilerState.prevFireTime(trigger.getPreviousFireTime());
		jobSchedilerState.setPriority(trigger.getPriority()+0l);
		jobSchedilerState.setTriggerState(scheduler.getTriggerState(trigger.getKey()).name());
		if (trigger instanceof CronTrigger) {
			jobSchedilerState.setTriggerType(Constants.TTYPE_CRON);
			jobSchedilerState.setTimeZoneId(((CronTrigger) trigger).getTimeZone().getID());
			jobSchedilerState.setCronExpression(((CronTrigger) trigger).getCronExpression());
		}else if(trigger instanceof CalendarIntervalTrigger) {
			jobSchedilerState.setTriggerType(Constants.TTYPE_CAL_INT);
			jobSchedilerState.setTimeZoneId(((CalendarIntervalTrigger) trigger).getTimeZone().getDisplayName());
		}else if(trigger instanceof SimpleTrigger) {
			jobSchedilerState.setTriggerType(Constants.TTYPE_SIMPLE);
		}else if(trigger instanceof DailyTimeIntervalTrigger) {
			jobSchedilerState.setTriggerType(Constants.TTYPE_DAILY_TIME_INT);
		}else {
			jobSchedilerState.setTriggerType(Constants.TTYPE_BLOB);
		}
		jobSchedilerState.setNonconcurrent((scheduler.getJobDetail(trigger.getJobKey()).isConcurrentExectionDisallowed()?
				ScheduleConstant.CONCURRENT_TYPE_N:ScheduleConstant.CONCURRENT_TYPE_Y)+"");
		jobSchedilerState.startTime(trigger.getStartTime());
		jobSchedilerState.endTime(trigger.getEndTime());
		jobSchedilerState.setMisfireInstr(trigger.getMisfireInstruction());
		jobSchedilerState.setJobData(CollectionUtil.isEmpty(trigger.getJobDataMap())?"":JsonUtil.toJsonString(trigger.getJobDataMap()));
		return jobSchedilerState;
    }

    /**
     * 查询定时调度列表
     * 
     * @param param
     * @return 
     */
    @Override
    @Transactional(readOnly = true)
    public List<ScheduleJob> selectList(ScheduleJobParam param) {
    
        QueryWrapper<ScheduleJob> queryWrapper = new QueryWrapper<>();
        queryWrapper.like(StringUtils.isNotBlank(param.getJobNameKey()), "job_name", param.getJobNameKey());
        queryWrapper.eq(StringUtils.isNotBlank(param.getJobName()), "job_name", param.getJobName());
        queryWrapper.eq(StringUtils.isNotBlank(param.getJobGroup()), "job_group", param.getJobGroup());
        queryWrapper.eq(Objects.nonNull(param.getStatus()), "status", param.getStatus());

        List<ScheduleJob> list = scheduleJobMapper.selectList(queryWrapper);
        if(CollectionUtil.isNotEmpty(list) && param.isWithscheduleStatus()) {
        	TaskUtils.setScheduleStatus(list);
        }
        return list;
    }

    /**
     * 新增定时调度任务
     * 
     * @param scheduleJob 
     * @return 
     */
    @Override
    @Transactional
    public int insert(ScheduleJob scheduleJob) {
    
    	TaskUtils.setDefaultVal(scheduleJob);
		int n = scheduleJobMapper.insert(scheduleJob);
		if(n > 0){
			try {
				scheduleManage.addJob(scheduleJob);
			} catch (Exception e) {
				logger.error("添加定时任务到定时任务列表失败", e);
				throw new BusinessException("任务添加成功，但添加到定时器失败");
			}
		}
		
        return n;
    }

    /**
     * 修改定时调度任务
     * 
     * @param scheduleJob 
     * @return 
     */
    @Override
    @Transactional
    public int update(ScheduleJob scheduleJob) {
    
    	ScheduleJob oldJob = getJob(scheduleJob.getId());
		TaskUtils.checkTaskIsRunning(oldJob);

		if(StringUtils.isBlank(scheduleJob.getJobGroup())){
			scheduleJob.setJobGroup(ScheduleConstant.DEFAULT_GROUP);
		}
		
		int n = scheduleJobMapper.updateById(scheduleJob);
		if(n > 0){
			try {
				scheduleManage.deleteJob(oldJob);
				if(TaskUtils.isActive(scheduleJob)){
					scheduleManage.addJob(scheduleJob);
				}
			} catch (Exception e) {
				logger.error("任务记录更新成功，但更新到定时器失败", e);
				throw new BusinessException("任务更新成功，但更新到定时器失败");
			}
		}
        return n;
    }
    
    @Override
    @Transactional
	public int active(ScheduleJob scheduleJob) {
		
		String id = scheduleJob.getId();
		if(StringUtils.contains(id, ",")){
			String[] ids = id.split(",");
			int n = 0;
			for (String idtmp : ids) {
				scheduleJob.setId(idtmp);
				n += activeSingle(scheduleJob);
			}
			return n;
		}else{
			return activeSingle(scheduleJob);
		}
	}

    @Transactional
	private int activeSingle(ScheduleJob scheduleJob){
		ScheduleJob job = getJob(scheduleJob.getId());
		TaskUtils.checkTaskIsRunning(job);
		
		ScheduleJob newJob = new ScheduleJob();
		newJob.setId(scheduleJob.getId());
		newJob.setStatus(scheduleJob.getStatus());
		int n = scheduleJobMapper.updateById(newJob);
		job.setStatus(scheduleJob.getStatus());
		try {
			if(TaskUtils.isActive(job)){
				scheduleManage.addJob(job);
			}else{
				scheduleManage.deleteJob(job);
			}
		} catch (SchedulerException e) {
			logger.error("更新定时任务到定时任务列表失败", e);
			throw new BusinessException("任务记录更新成功，但更新到定时器失败");
		}
		return n;
	}
	
    /**
     * 删除定时调度信息
     * 
     * @param idStr ID，多个用逗号分隔
     * @return 
     */
    @Override
    @Transactional
    public int deleteById(String idStr) {
    	if(StringUtils.isBlank(idStr)) {
            return 0;
    	}
    	int n = 0;
    	for (String id : StringUtils.str2List(idStr)) {
    		ScheduleJob job = getJob(id);
            int i = scheduleJobMapper.deleteById(id);
            if(i>0) {
            	try {
					scheduleManage.deleteJob(job);
				} catch (SchedulerException e) {
					logger.error("移除定时任务异常", e);
					throw new BusinessException("移除定时任务异常， id:"+id);
				}
            	n += i;
            }
        }
        return n;
    }
    
    @Override
    public int execute(String id) {
		ScheduleJob job = getJob(id);
		TaskUtils.checkTaskIsRunning(job);
		
		Map<String, Object> param = JsonUtil.parseJSON(job.getExeParam());
		if(param == null){
			param = new HashMap<>();
		}
		
		ScheduleJobLog jobLog = new ScheduleJobLog(job, ScheduleConstant.TRIGGER_TYPE_MANUAL).start();
		jobLog.setCreateBy(TokenUtils.getLoginUsername());
		param.put(ScheduleConstant.CUR_EXE_LOG, jobLog);// 当前执行日志
		
		try {
			if(scheduleManage.isExist(job)){
				scheduleManage.runJobNow(job, param);
			}else{
				//异步执行
				final Map<String, Object> finalParam = param;
				AsyncManager.execute(job.getJobKey(), ()->{
					new ManualAsyncJob().run(job, finalParam, jobLog);
				});
			}
		} catch (BaseException e) {
			logger.error("执行定时任务失败:{}", e.getMessage());
			jobLog.setExeStatus(ScheduleConstant.EXE_STATUS_FAIL);
			jobLog.setExeMsg(e.getMessage());
			throw e;
		} catch (Exception e) {
			logger.error("执行定时任务异常", e);
			jobLog.setExeStatus(ScheduleConstant.EXE_STATUS_FAIL);
			jobLog.setExeMsg("执行异常："+e.getMessage());
			throw new BusinessException("执行定时任务异常");
		} finally {
			if(jobLog.getExeStatus() == ScheduleConstant.EXE_STATUS_FAIL && jobLog.collectLog()) {
				scheduleJobLogService.updateResult(jobLog);
			}
		}
		return 1 ;
	}
    
    /**
     * 导入定时调度数据
     * @param dataList 定时调度数据列表
     * @param isUpdateSupport 是否更新支持，如果已存在，则进行更新数据
     * @return 保存失败的记录
     */
    @Override
    @Transactional
    public void importData(ImportWrapper<ScheduleJobImp, ScheduleJob> wrapper) {
        
    	UniqueDesc uniqueDesc = ValidateUtil.selectUniqueDesc(ScheduleConstant.UNIQUE_JOB_NAME);
    	importData(wrapper, new AbstractImportBody<ScheduleJobImp, ScheduleJob>() {
			@Override
			public ScheduleJob selectRecord(ImportEntry<ScheduleJobImp, ScheduleJob> entry) {
				return ValidateUtil.selectUniqueData(entry.getData(), ValidOperType.ADD, uniqueDesc, ScheduleJob.class);
			}
			@Override
			public int insertData(ScheduleJob data) {
				return insert(data);
			}
			@Override
			public int updateData(ScheduleJob data) {
				return update(data);
			}
			@Override
			public String notUniqueMsg(ImportEntry<ScheduleJobImp, ScheduleJob> entry) {
				return MessageFormat.format("任务名为【{0}】的记录已存在", entry.getData().getJobName());
			}
    	});
    }
    
}
